package blog.network;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import javax.microedition.io.ConnectionNotFoundException;
import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;

import org.kxml2.io.KXmlParser;
import org.kxml2.io.KXmlSerializer;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import blog.*;
import blog.gui.IBaseGui;

/**
 * A wholesome class for sending XMLRPC requests to an XMLRPC server
 * and receiving and processing the result responses into useful
 * Java objects.
 * 
 */
public class XmlRpcClient2 implements ProgressStatus
{

	public static final String RPCNAME_GET_USERS_BLOGS = "blogger.getUsersBlogs";

	public static final String RPCNAME_POST_FULL_ENTRY = "metaWeblog.newPost";

	public static final String RPCNAME_POST_SIMPLE_ENTRY = "blogger.newPost";

	public static final String RPCNAME_POST_MEDIA_OBJ = "metaWeblog.newMediaObject";

	//public static final String RPCNAME_MT_GET_CATLIST = "mt.getCategoryList";

	//public static final String RPCNAME_MW_GET_RECENT_POSTS = "metaWeblog.getRecentPosts";

	protected int progressState = 0;

	protected Object asyncResult;
	
	private IBaseGui waitGui;
	
	int tryCount;
	
	//protected InputStream is;
	
	public void setWaitGui( IBaseGui wait )
	{
		waitGui = wait;
	}

	/**
	 * The complete URL to be used to connect to the XMLRPC server.
	 */
	protected String accessURL;

	/**
	 * The current connection to the server.
	 */
	protected HttpConnection serverCon; //javax.microedition.io.HttpConnection

	/**
	 * Used to write XMLRPC data to the server.
	 */
	protected KXmlSerializer xmlWriter;

	/**
	 * Used to read XMLRPC response data.
	 */
	protected KXmlParser xmlParser;

	protected Vector parseParams;

	/**
	 * Constructs KXmlRpcClient that will access the server given by the URL.
	 *
	 * @param URL Complete URL for accessing the XMLRPC server.
	 */
	public XmlRpcClient2(String URL)
	{
		accessURL = URL;
	}//KXmlRpcClient

	/**
	 * Set the access URL 
	 * @param URL with which to access the XMLRPC server.
	 */
	public void setTargetURL(String URL)
	{
		accessURL = URL;
	}
	
	public void clearConn()
	{
		if ( this.serverCon!=null )
		{
			try
			{
				this.serverCon.close( );
			}
			catch (IOException e)
			{
			}
			this.serverCon = null;
		}
	}

	/**
	 * Executes a remote procedure call on the XMLRPC server.
	 * Prior to calling this method, you must set the server's URL properly.
	 *
	 * @param method The name of the XMLRPC method to be called on the server.
	 * @param params A list of params required by the method call.
	 * @return The result returned by the server-- typically a Hashtable or null
	 */
	public Object remoteCall(String method, Vector params) throws XmlRpcFault, IOException
	{

		this.asyncResult = null;
		int resultCode = 0;

		try
		{
			progressState = 0; waitGui.notifyChanged( new Integer(progressState) );
			
			resultCode = writeRequest( method, params );

			if (resultCode == HttpConnection.HTTP_OK)
			{
				progressState = 50; waitGui.notifyChanged( new Integer(progressState) );
				asyncResult = readResponse( );
				progressState = 90; waitGui.notifyChanged( new Integer(progressState) );
			}
			else
			{
				asyncResult = new XmlRpcFault( resultCode, "HTTP Error" );
			}
		}
		catch (XmlRpcFault parseEx)
		{
			asyncResult = parseEx;
		}
		catch (ConnectionNotFoundException connEx)
		{
			asyncResult = connEx;
		}
		catch (IOException ioEx)
		{
			ioEx.printStackTrace( );
			asyncResult = ioEx;
		}
		finally
		{
			try
			{
				if (serverCon != null)
				{
					serverCon.close( );
					serverCon = null;
				}
			}
			catch (IOException ioe)
			{
				ioe.printStackTrace( );
			}
			catch (Exception ex)
			{
			}
		}//end try/catch/finally


		if (asyncResult instanceof XmlRpcFault)
			throw ((XmlRpcFault) asyncResult);
		else if (asyncResult instanceof Exception)
			throw ((IOException) asyncResult);

		return asyncResult;
	}// remoteCall

	/**
	 * Write the HTTP request to the remote xmlrpc server.
	 */
	protected int writeRequest(String method, Vector params) throws IOException
	{
		int rc = 200;
		boolean bKeepGoing;
		String newURL;
		//create an output stream to write out the data on
		ByteArrayOutputStream bos = new ByteArrayOutputStream( RPCNAME_POST_MEDIA_OBJ.equals( method ) ? 4096 : 1024 );//should be adequate for most requests

		progressState += 5; waitGui.notifyChanged( new Integer(progressState) );
		waitGui.notifyChanged(ConstString.PROMPT_NETWORK_COMPOSE_DATA);
		writeXmlRpcCall( method, params, bos );
		progressState += 5; waitGui.notifyChanged( new Integer(progressState) );

		byte[] requestData = bos.toByteArray( );
		bos = null; //no longer used
		newURL = this.accessURL;

		bKeepGoing = true;
		
		tryCount = 1;
		OutputStream out = null;

		do
		{
			waitGui.notifyChanged(ConstString.PROMPT_NETWORK_INITIALIZING);
			if ( tryCount==1 )
			{
			serverCon = (HttpConnection) Connector.open( newURL, Connector.READ_WRITE );
			serverCon.setRequestMethod( HttpConnection.POST );
			serverCon.setRequestProperty( "Accept", "*/*" );
			serverCon.setRequestProperty( "Accept-Language", "en-US, en, *");
			serverCon.setRequestProperty( "Content-Type", "text/xml" );
			//serverCon.setRequestProperty( "User-Agent", "MyBlog-j2me/1.0.0" );
			serverCon.setRequestProperty( "Content-Length", Integer.toString( requestData.length ) );
			serverCon.setRequestProperty("Connection", "Keep-Alive");
			//serverCon.setRequestProperty("Protocol", "HTTP/1.0");

			//
			//============ WARNING ======================
			//Right now the WTK emulator removes the Content-Length header above 2kB
			//and switches to chunked transfer-encoding.  Unfortunately, Apache and some other
			//web servers don't like that.  This switch appears to violate the intent of using chunking,
			//since we know and set the Content-Length, but the WTK HTTP implementation chooses to
			//discard that information. 
			//
			//We set the Content-Length header and assume the HttpConnection does the right thing-- which
			//it does on most handsets, just not the reference J2ME-WTK implemenation.
			//

			//The following code checks some props related to this WTK issue
			//if "use_chunks' is turned on, it appears there's no way to turn it off
			//the "output_buffer_size" dictates how the size at which the httpconn switches over to chunking


			//Sometimes we need to make sure the Host property is set correctly: otherwise, on a server
			//that serves multiple virtual domains, it wouldn't know which Host to serve.
			String trueHost = serverCon.getHost( );

			if (trueHost == null)
			{

				int dblSlashPos = newURL.indexOf( "//" );
				if (dblSlashPos > -1)
				{
					String newHost;
					int dirSlashPos = newURL.indexOf( "/", dblSlashPos + 2 );
					if (dirSlashPos > -1)
					{
						newHost = newURL.substring( dblSlashPos + 2, dirSlashPos );
					}
					else newHost = newURL.substring( dblSlashPos + 2 );

					serverCon.setRequestProperty( "Host", newHost );
				}
			}

			// Write the request to the server.

			if ( method!=RPCNAME_POST_MEDIA_OBJ )
			{
				waitGui.notifyChanged(ConstString.PROMPT_NETWORK_SENDING_DATA);
			}
			else
			{
				waitGui.notifyChanged(ConstString.PROMPT_NETWORK_UPLOADING_MEDIA);
			}
			
			out = serverCon.openOutputStream( );
			out.write( requestData, 0, requestData.length );
			//out.flush(); // Optional, getResponseCode will flush as well, but chunking behavior may be different?


			int tryGetCode = 1;
			while( tryGetCode<=3 )
			{
//				try {
//					Thread.sleep(1000L);
//				} catch (InterruptedException e) {
//				}
				try
				{
					rc = serverCon.getResponseCode( );

					
					{
						break;
					}
				}
				catch (IOException respCodeEx)
				{
					rc = 666;
				}
				tryGetCode++;
//				if ( rc!=666 && serverCon.getLength( )>0 )
//				{
//					break;
//				}
//				tryGetCode++;
			}
			}
//			else
//			{
//				String serverAddress, path;
//				int start, end;
//				if ( newURL.startsWith("http://") )
//				{
//					start = 7;
//				}
//				else 
//				{
//					start = 0;
//				}
//				end = newURL.indexOf("/", start);
//				if ( end==-1 )
//				{
//					serverAddress = newURL.substring(start);
//					path = "/";
//				}
//				else
//				{
//					serverAddress = newURL.substring(start, end);
//					path = newURL.substring(end);
//				}
//				if ( serverAddress.indexOf(":")==-1 )
//				{
//					serverAddress += ":80";
//				}
//				StringBuffer buf = new StringBuffer(200);
//				buf.append("POST ").append(path).append(" HTTP/1.0\r\n");
//				buf.append("Accept: */*\r\nAccept-Language: en-US, en, *\r\nUser-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Windows Live Writer 1.0)\r\nContent-Type: text/xml\r\nHost: www.buzzermatrix.com\r\n");
//				buf.append("Content-Length: ").append(requestData.length).append("\r\n");
//				SocketConnection sc = (SocketConnection) Connector.open("socket://"+serverAddress);
//				InputStream is = sc.openInputStream();
//				out = sc.openOutputStream();
//				out.write(buf.toString().getBytes());
//				out.write(requestData);
//				out.flush();
//				ByteArrayOutputStream bais = new ByteArrayOutputStream();
//				byte[] buffer = new byte[256];
//				int len;
//				while( (len=is.read(buffer))>0 )
//				{
//					bais.write(buffer, 0, len);
//				}
//				String result = new String(bais.toByteArray());
//				start = result.indexOf("<? xml");
//				this.is = new ByteArrayInputStream(result.substring(start).getBytes());
//				is.close();
//				out.close();
//				sc.close();
//			}

			switch (rc)
			{
			case HttpConnection.HTTP_MOVED_PERM:
			case HttpConnection.HTTP_MOVED_TEMP:
			case HttpConnection.HTTP_SEE_OTHER:
			case HttpConnection.HTTP_TEMP_REDIRECT:
				newURL = serverCon.getHeaderField( "Location" );
				if (newURL != null && newURL.startsWith( "/*" ))
				{
					StringBuffer b = new StringBuffer( );
					b.append( "http://" );
					b.append( serverCon.getHost( ) );
					b.append( ':' );
					b.append( serverCon.getPort( ) );
					b.append( newURL );
					newURL = b.toString( );
				}

				if ( tryCount==1 )
				{
					out.close( );
					out = null;
					serverCon.close( );
					serverCon = null;
				}
				break;
				
			case 666:
				tryCount++;
				break;

			case HttpConnection.HTTP_OK:
			default:
				newURL = null;
				bKeepGoing = false;
				break;
			} //switch rc

		} while (bKeepGoing && tryCount<=1);

		progressState += 5; waitGui.notifyChanged( new Integer(progressState) );

		return rc;
	}//writeRequest

	/**
	 * Read response from server, parse it, and return results.
	 *
	 */
	protected Object readResponse() throws XmlRpcFault, IOException
	{
		InputStream in = null;
		Object result = null;
		long availLen = 0;

		waitGui.notifyChanged(ConstString.PROMPT_NETWORK_READING_DATA);
		
//		if ( tryCount==1 )
//		{
			in = serverCon.openInputStream( );
			availLen = serverCon.getLength( );
//		}
//		else
//		{
//			in = is;
//		}

		if (availLen < 0)
		{
			int headAvail = serverCon.getHeaderFieldInt( "Content-Length", -666 );
			if (headAvail > 0)
				availLen = headAvail;
			else if (headAvail < 0)
				availLen = 999999; //XXX ludicrously large data
		}

		if (!(availLen > 0))
			return null;
		progressState += 5; waitGui.notifyChanged( new Integer(progressState) );

		// Parse response from server
		if (in != null)
		{
			progressState += 5; waitGui.notifyChanged( new Integer(progressState) );
			
			waitGui.notifyChanged(ConstString.PROMPT_NETWORK_DEALING_WITH_DATA);
			
			if ( availLen==999999 )
			{
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
				//baos.write("\r\n".getBytes());
				int c;
				while( (c=in.read())!=-1 )
				{
					baos.write(c);
				}
				in.close();
				in = null;
				String str = new String(baos.toByteArray(), "UTF-8");
				int index = str.indexOf("<");
				if ( index>0 )
				{
					str = str.substring(index);
				}
				in = new ByteArrayInputStream( str.getBytes() );
			}

			result = parseXmlRpcResponse( in );
			progressState += 5; waitGui.notifyChanged( new Integer(progressState) );
		}
		else
		{
			throw new IOException( "openInputStream failed" );
		}
		
		in.close();

		return result;
	}//readResponse

	//-- Methods for writing requests to the server
	/**
	 * Builds an XMLRPC document (consisting of XML data).
	 *
	 * @param methodName The name of the remote procedure to call.
	 * @param params Parameters to be passed into the remote procedure call.
	 */
	protected void writeXmlRpcCall(String methodName, Vector params, OutputStream bos) throws IOException
	{

		this.xmlWriter = new KXmlSerializer( );
		xmlWriter.setOutput( bos, "UTF-8" );
		
		bos.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>".getBytes());

		//start <methodCall>
		xmlWriter.startTag( null, "methodCall" );
		//start <methodName>
		xmlWriter.startTag( null, "methodName" );
		xmlWriter.text( methodName );
		xmlWriter.endTag( null, "methodName" );// end </methodName>

		//if params is non-null write params to the server
		if (params != null)
		{
			//start <params> if it's non-null (it might be empty?)
			xmlWriter.startTag( null, "params" );
			for (int i = 0; i < params.size( ); i++)
			{
				xmlWriter.startTag( null, "param" ); //start <param>
				writeXmlRpcValue( params.elementAt( i ) );//write out the value of the given param
				xmlWriter.endTag( null, "param" ); //end </param>
			}
			xmlWriter.endTag( null, "params" ); //end </params> 
		}

		xmlWriter.endTag( null, "methodCall" );//end </methodCall>

		xmlWriter.flush( ); //make sure the entire atomic call is flushed out the pipe
		xmlWriter = null; //release writer when finished
	}//writeXmlRpcCall

	/**
	 * Marshalls Java values into XMLRPC-formatted xml data.
	 */
	private void writeXmlRpcValue(Object value) throws IOException
	{
		String valStr;
		//start <value>

		xmlWriter.startTag( null, "value" );

		//place "lengthiest" types at the top of this list

		// Hashtable is shipped as an XMLRPC <struct> 
		// can have an arbitrary number of fields 

		if (value instanceof Hashtable)
		{
			Hashtable h = (Hashtable) value;
			xmlWriter.startTag( null, "struct" );//start <struct>
			for (Enumeration e = h.keys( ); e.hasMoreElements( );)
			{
				Object key = e.nextElement( );
				xmlWriter.startTag( null, "member" );//start <member>
				writeWholeXmlTag( "name", key.toString( ) ); //<name>foo</name>
				//now recurse to write out the value associated with this key (name)


				writeXmlRpcValue( h.get( key ) );
				xmlWriter.endTag( null, "member" );//end  </member>
			}//foreach key

			xmlWriter.endTag( null, "struct" ); //</struct>
		}
		else if (value instanceof Vector)
		{ //encode java Vectors as XMLRPC arrays
			Vector v = (Vector) value;
			xmlWriter.startTag( null, "array" ); //<array>
			for (int i = 0; i < v.size( ); i++)
			{
				Object curObj = v.elementAt( i );
				//recursively write all the items in the array
				writeXmlRpcValue( curObj );
			}//foreach item in array
			xmlWriter.endTag( null, "array" );// </array>
		}
		else if (value instanceof String)
		{
			writeWholeXmlTag( "string", (String) value );//<string>foobar</string>
		}
		else if (value instanceof byte[])
		{
			//encode byte arrays as base64

			try
			{
				byte[] dataBlock = (byte[]) value;
				System.gc( );
				StringBuffer stringBuf = new StringBuffer( (dataBlock.length * 3) / 2 );
				Base64.encode( dataBlock, 0, dataBlock.length, stringBuf );

				try
				{
					//valStr = stringBuf.toString();
					valStr = null;

					writeWholeXmlTag_Safe( "base64", stringBuf );//<base64>foobar</base64>
					stringBuf = null;
				}
				catch (java.lang.OutOfMemoryError oomErr)
				{
				}
			}
			catch (java.lang.OutOfMemoryError oom)
			{
			}

		}
		else if (value instanceof Date)
		{
			//encode dates in ISO8601 format
			valStr = DateConverter.dateToIso8601String( (Date) value, DateConverter.DATE_TIME );
			writeWholeXmlTag( "dateTime.iso8601", valStr );//<dateTime.iso8601>foobar</dateTime.iso8601>
		}
		else if (value instanceof Integer)
		{
			valStr = ((Integer) value).toString( );
			writeWholeXmlTag( "int", valStr ); //we could also use <i4> but this seems cleaner ?
		}
		else if (value instanceof Boolean)
		{
			valStr = (((Boolean) value).booleanValue( ) ? "1" : "0");
			writeWholeXmlTag( "boolean", valStr );
		}
		else
		{
			//We are sloppy and loose here:
			//We encode unknown objects as strings.
			//This gets interesting when we're talking about float values
			//such as doubles-- if the ROM supports doubles, then Double.toString
			//should return the right thing....right?
			writeWholeXmlTag( "string", value.toString( ) );
		}

		xmlWriter.endTag( null, "value" );//end </value>

	}//writeXmlRpcValue

	/**
	 * Handy method for writing out a complete <starttag>value</endtag>
	 * Assumes xmlWriter is already running
	 */
	protected void writeWholeXmlTag(String tagType, String value) throws IOException
	{
		xmlWriter.startTag( null, tagType ); //start <tagname>
		xmlWriter.text( value );
		xmlWriter.endTag( null, tagType );//end </tagname>
	}//writeWholeXmlTag

	protected void writeWholeXmlTag_Safe(String tagType, StringBuffer valBuf) throws IOException
	{
		char[] charBlock = new char[256];
		int srcBegin, srcEnd, total = valBuf.length( ), remain;

		xmlWriter.startTag( null, tagType ); //start <tagname>

		for (srcBegin = 0, remain = total; srcBegin < total;)
		{
			int blockSize = (remain < 256) ? remain : 256;
			srcEnd = srcBegin + blockSize;
			valBuf.getChars( srcBegin, srcEnd, charBlock, 0 );

			srcBegin += blockSize;
			remain -= blockSize;
			try
			{
				xmlWriter.text( charBlock, 0, blockSize );
			}
			catch (java.lang.OutOfMemoryError oom)
			{
				break;
			}
		}

		xmlWriter.endTag( null, tagType );//end </tagname>

	}

	//-- Methods for reading response from XMLRPC server

	/**
	 * Parse a single XMLRPC <fault>
	 */
	public XmlRpcFault parseXmlRpcFault() throws XmlPullParserException, IOException
	{

		int fCode = -1;
		String stringy = "Unknown XMLRPC Fault";

		if (XmlPullParser.START_TAG == xmlParser.nextTag( ))
		{

			if ("value".equals( xmlParser.getName( ) ))
			{
				// XmlRPC faults are returned as structs (which are mapped to Hashtables)
				Hashtable fault = (Hashtable) parseXmlRpcValue( );

				// Ultimately, a client-side exception object is generated
				try
				{
					stringy = (String) fault.get( "faultString" );
					if (stringy.indexOf( "login" ) >= 0)
						fCode = XmlRpcFault.UNAUTHORIZED;
					else if (stringy.indexOf( "Invalid blog" ) >= 0)
						fCode = XmlRpcFault.BAD_TARGET;
					else if (stringy.indexOf( "UserHasNoBlogsException" ) > 0)
						fCode = XmlRpcFault.USER_HAS_NO_BLOGS;
					else fCode = ((Integer) fault.get( "faultCode" )).intValue( );
				}
				catch (Throwable baddy)
				{
				}
				;
			}

		}

		return new XmlRpcFault( fCode, stringy );
	}

	/** 
	 * Called by a client to parse an XMLPC response returned by the server.
	 *
	 * @return The response parameter sent by the server.
	 */
	public Object parseXmlRpcResponse(InputStream in) throws XmlRpcFault, IOException
	{
		Object result = null;
		try
		{
			this.xmlParser = new KXmlParser( );
			this.xmlParser.setInput( in, "UTF-8" );
			this.parseParams = new Vector( );

			xmlParser.nextTag( );
			xmlParser.require( XmlPullParser.START_TAG, null, "methodResponse" );

			if (XmlPullParser.START_TAG == xmlParser.nextTag( ))
			{
				String tag = xmlParser.getName( );

				if ("fault".equals( tag ))
				{
					throw (parseXmlRpcFault( ));
				}
				else if ("params".equals( tag ))
				{
					parseXmlRpcParams( );
					//XXX should we ever allow params.size > 1 ?
					if (parseParams.size( ) > 1)
						throw new IOException( "Too many return parameters" );
					else if (parseParams.size( ) == 1)
						result = parseParams.elementAt( 0 );
				}
				else throw new XmlRpcFault( XmlRpcFault.PARSE_FAILURE, "<fault> or <params> expected, got: " + tag );
			}
		}
		catch (XmlPullParserException parseEx)
		{
			throw (new XmlRpcFault( parseEx ));
		}
		finally
		{
			xmlParser = null; //release when finished
		}

		return result;
	}//parseXmlRpcResponse

	/**
	 * All data in an XML-RPC call is passed as a parameter. This method parses 
	 * the parameter values out of each parameter by calling the parseXmlRpcValue() 
	 * method. 
	 */
	void parseXmlRpcParams() throws IOException, XmlPullParserException
	{
		while (XmlPullParser.START_TAG == xmlParser.nextTag( ))
		{
			if ("param".equals( xmlParser.getName( ) ))
			{
				if (XmlPullParser.START_TAG == xmlParser.nextTag( ))
				{
					if ("value".equals( xmlParser.getName( ) ))
					{
						Object parmVal = parseXmlRpcValue( );
						parseParams.addElement( parmVal );
					}
					else break;
				}
			}
			else break;
		}
	}//parseXmlRpcParams

	/** 
	 * Parse XMLRPC values into Java objects. 
	 * 
	 * @return  Object representation of the XMLRPC value
	 */
	Object parseXmlRpcValue() throws IOException, XmlPullParserException
	{

		Object result = null;

		while (XmlPullParser.START_TAG == xmlParser.nextTag( ))
		{

			String tag = xmlParser.getName( );

			if ("struct".equals( tag ))
				result = parseXmlRpcStruct( );
			else if ("array".equals( tag ))
				result = parseXmlRpcArray( );
			else if ("base64".equals( tag ))
				result = Base64.decode( xmlParser.nextText( ) );
			else if ("int".equals( tag ) || "i4".equals( tag ))
				result = Integer.valueOf( xmlParser.nextText( ) );
			else if ("boolean".equals( tag ))
				result = new Boolean( xmlParser.nextText( ).trim( ).equals( "1" ) );
			else if ("dateTime.iso8601".equals( tag ))
				result = DateConverter.stringToDate( xmlParser.nextText( ), DateConverter.DATE_TIME );
			//else if ("double".equals(tag))
			//   result = xmlParser.nextText();
			//else if ("string".equals(tag))
			//   result = xmlParser.nextText();
			else
			{
				// "double" "string" or unknown....
				result = xmlParser.nextText( );
			}
		}

		return result;
	}//parseXmlRpcValue

	/**
	 * Maps XML-RPC structs to java.util.Hashtables
	 */
	void parseXmlRpcStructMember(Hashtable h) throws IOException, XmlPullParserException
	{

		while (XmlPullParser.START_TAG == xmlParser.nextTag( ))
		{

			if ("name".equals( xmlParser.getName( ) ))
			{

				String memberName = xmlParser.nextText( );
				Object memberVal = null;

				if (XmlPullParser.START_TAG == xmlParser.nextTag( ))
				{
					if ("value".equals( xmlParser.getName( ) ))
					{
						memberVal = parseXmlRpcValue( );
					}
				}

				h.put( memberName, memberVal );
			}
			else break; //unknown struct member?

		}

	}//parseXmlRpcStructMember

	/**
	 * Parse an XMLRPC <struct> into a Java object representation.
	 * 
	 * @return Hashtable representing struct member name / member value pairs
	 * @throws IOException
	 * @throws XmlPullParserException
	 */
	Hashtable parseXmlRpcStruct() throws IOException, XmlPullParserException
	{

		Hashtable h = new Hashtable( );

		while (XmlPullParser.START_TAG == xmlParser.nextTag( ))
		{

			if ("member".equals( xmlParser.getName( ) ))
			{
				parseXmlRpcStructMember( h );
			}

		}

		/*
		 while(true) {
		 int eventType = xmlParser.nextTag();
		 if (eventType == XmlPullParser.START_TAG) {
		 String tag = xmlParser.getName();
		 
		 if ("member".equals(tag)) {
		 parseXmlRpcStructMember(h);
		 }              
		 }
		 else
		 break;
		 }
		 */

		return h;
	}//parseXmlRpcStruct

	/**
	 * Maps XML-RPC arrays to a Java Vector
	 * 
	 * @return Vector  representing XMLRPC <array>
	 */
	Vector parseXmlRpcArray() throws IOException, XmlPullParserException
	{
		Vector v = new Vector( );

		if (XmlPullParser.START_TAG == xmlParser.nextTag( ))
		{
			//20021027 TCS: Need to skip <data> tag!
			if ("data".equals( xmlParser.getName( ) ))
			{
				while ((XmlPullParser.START_TAG == xmlParser.nextTag( )) && "value".equals( xmlParser.getName( ) ))
				{
					v.addElement( parseXmlRpcValue( ) );
				}
			}
		}

		return v;
	}//parseXmlRpcArray

	//--- Methods for supporting KProgressStatus
	/** @ return Zero if no failure, or the nonzero error code;
	 */
	public int getFailureCode()
	{
		int fcode = 0;

		if (asyncResult != null)
		{
			if (asyncResult instanceof XmlRpcFault)
			{
				fcode = ((XmlRpcFault) asyncResult).code;
				if (fcode == XmlRpcFault.UNAUTHORIZED)
					fcode = ProgressStatus.ERR_BAD_USER;
				else if (fcode == XmlRpcFault.USER_HAS_NO_BLOGS)
					fcode = ProgressStatus.ERR_USER_HAS_NO_BLOGS;
			}
			else if (asyncResult instanceof Exception)
			{
				fcode = ProgressStatus.ERR_IO;
			}
		}

		return fcode;

	}//getFailureCode

	/** @return The progress of this item, from 0 to 100
	 */
	public int getProgress()
	{
		return progressState;
	}

	/** @return True if operation can be cancelled, false if not
	 */
	public boolean isCancellable()
	{
		return false; //none of the operations can be cancelled currently.
	}

	/** Attempt to cancel the operation in progress. This may not work
	 * if isCancellable returns false.
	 */
	public void cancel()
	{
	}

	/** 
	 * reset progress to zero
	 */
	public void reset()
	{
		progressState = 0; waitGui.notifyChanged( new Integer(progressState) );
		asyncResult = null;
	}

}//XmlRpcclient

/*
 class DbgInputReader extends InputStreamReader
 {
 public DbgInputReader (InputStream in) {
 super(in);
 }
 
 public int read(char[] cbuf, int off, int len) throws IOException {
 int numRead = super.read(cbuf,off,len);
 if (numRead > 0) {
 String dataStr = new String(cbuf,off,numRead);
 }
 
 return numRead;
 }
 
 public int read(char[] cbuf)  throws IOException {
 return this.read(cbuf,0,cbuf.length);
 }
 
 public int read() throws IOException {
 int val = super.read();
 System.out.print((char)val);
 return val;
 
 }

 }//DbgInputReader
 */

